﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.ComponentModel;

namespace CommunicationServer
{
    public class TCPClient:IDisposable 
    {
        #region Variables
        #region Events
        public delegate void MessageReceivedDelegate(string Message);
        public event MessageReceivedDelegate MessageReceived;

        public delegate void ConnectionEstablishedDelegate();
        public event ConnectionEstablishedDelegate ConnectionEstablished;

        public delegate void ConnectionTerminatedDelegate();
        public event ConnectionTerminatedDelegate ConnectionTerminated;
        #endregion

        #region TCP Objects
        private TcpClient tclClient;
        private NetworkStream nstClient;
        private IPEndPoint iepServer;
        #endregion

        #region Properties
        private IPAddress _ServerIPAddress;
        private int _ServerPort = 0;
        private Encoding _TextEncoding = System.Text.Encoding.UTF8;
        #endregion

        #region Properties
        public IPAddress ServerIPAddress
        {
            get { return this._ServerIPAddress; }
            set { this._ServerIPAddress = value; }
        }
        public int ServerPort
        {
            get { return this._ServerPort; }
            set { this._ServerPort = value; }
        }
        public bool Connected
        {
            get { return this.tclClient.Connected; }
        }
        public Encoding TextEncoding
        {
            get { return this._TextEncoding; }
            set { this._TextEncoding = value; }
        }
        #endregion
        #endregion

        #region Thread Safe Event Raisers
        private void RaiseConnectionEstablishedEvent()
        {
            // Check if delegate is assigned
            if (ConnectionEstablished != null)
            {
                // Check invoke status
                ISynchronizeInvoke syncInvoke = ConnectionEstablished.Target as ISynchronizeInvoke;
                if (syncInvoke != null && syncInvoke.InvokeRequired)
                {
                    // Invokie the event on the main thread
                    syncInvoke.Invoke(ConnectionEstablished, new object[] { });
                }
                else
                {
                    // Raise the event
                    ConnectionEstablished.Invoke();
                }
            }
        }
        private void RaiseConnectionTerminatedEvent()
        {
            // Check if delegate is assigned
            if (ConnectionTerminated != null)
            {
                // Check invoke status
                ISynchronizeInvoke syncInvoke = ConnectionTerminated.Target as ISynchronizeInvoke;
                if (syncInvoke != null && syncInvoke.InvokeRequired)
                {
                    // Invokie the event on the main thread
                    syncInvoke.Invoke(ConnectionTerminated, new object[] {});
                }
                else
                {
                    // Raise the event
                    ConnectionTerminated.Invoke();
                }
            }
        }
        private void RaiseMessageReceivedEvent(string Message)
        {
            // Check if delegate is assigned
            if (MessageReceived != null)
            {
                // Check invoke status
                ISynchronizeInvoke syncInvoke = MessageReceived.Target as ISynchronizeInvoke;
                if (syncInvoke != null && syncInvoke.InvokeRequired)
                {
                    // Invokie the event on the main thread
                    syncInvoke.Invoke(MessageReceived, new object[] { Message });
                }
                else
                {
                    // Raise the event
                    MessageReceived.Invoke(Message);
                }
            }
        }
        #endregion

        #region Private Methods
        private void CloseConnection()
        {
            // Close network stream
            try
            {
                if (nstClient != null)
                {
                    nstClient.Close();
                }
            }
            catch
            {
            }
            // Close TCPClient
            try
            {
                if (tclClient != null)
                {
                    if (tclClient.Connected)
                    {
                        tclClient.Client.Disconnect(false);
                    }
                    tclClient.Close();
                }
            }
            catch
            {
            }             
        }
        private void WaitForMessages()
        {
            // Start reading from network stream
            try
            {
                nstClient.BeginRead(new byte[1024], 0, 0, new AsyncCallback(ClientMessageReceived), nstClient);
            }
            catch (ObjectDisposedException odeExcp)
            {
                return;
            }
        }
        private void ClientMessageReceived(IAsyncResult iar)
        {
            // Define IAsyncResult network stream
            NetworkStream nsX = null;
            try
            {
                // Get network stream
                nsX = (NetworkStream)iar.AsyncState;
                // End read
                int intEndPoint = nsX.EndRead(iar);
                // Get received data
                string strReceivedData = "";
                while (nsX.DataAvailable)
                {
                    byte[] bytReceivedData = new byte[1024];
                    int a = nsX.Read(bytReceivedData, 0, 1024);
                    strReceivedData += this.TextEncoding.GetString(bytReceivedData);
                }
                // Process incoming data
                if (strReceivedData.Length > 0)
                {
                    // Send event
                    this.RaiseMessageReceivedEvent(strReceivedData);
                    //this.MessageReceived(strReceivedData);
                }
                else
                {
                    // No incoming data the connection is lost
                    this.Disconnect();
                    // Return
                    return;
                }
                // Continue reading
                nsX.BeginRead(new byte[1024], 0, 0, new AsyncCallback(ClientMessageReceived), nsX);
            }
            catch (ObjectDisposedException odeExcp)
            {
                return;
            }
            catch (Exception excp)
            {
                //continue reading
                try
                {
                    nsX.BeginRead(new byte[1024], 0, 0, new AsyncCallback(ClientMessageReceived), nsX);
                }
                catch
                {
                    // Can not continue so disconnect
                    this.Disconnect();
                }
            }            
        }
        private void SendingMessage(IAsyncResult iar)
        {
            // Get network stream
            NetworkStream nstX = (NetworkStream)iar.AsyncState;
            // End write of network stream
            try
            {
                //nstClient.EndWrite(iar);
                nstX.EndWrite(iar);
            }
            catch (ObjectDisposedException odeExcp)
            {
                // Object disposed
                return;
            }
        }
        #endregion

        #region Public Methods
        public bool Connect()
        {
            // Define return variable
            bool bRetval = false;
            // Close any existing connection
            this.CloseConnection();
            // Create ipendpoint object for Server
            try
            {
                iepServer = new IPEndPoint(this.ServerIPAddress, this.ServerPort);
            }
            catch
            {
                // Return
                return bRetval;
            }
            // Create new tcpclient object
            tclClient = new TcpClient();
            tclClient.NoDelay = true;

            tclClient.ReceiveBufferSize = 1024;
            tclClient.SendBufferSize = 1024;
            tclClient.Client.NoDelay = true;
            tclClient.Client.ReceiveBufferSize = 1024;
            tclClient.Client.SendBufferSize = 1024;
            
            // Try to connect
            try
            {
                tclClient.Connect(this.ServerIPAddress, this.ServerPort);
            }
            catch
            {
                // Return
                return bRetval;
            }
            // Get Stream of the connection
            try
            {
                nstClient = tclClient.GetStream();
                // Wait for server messages
                this.WaitForMessages();
                // Set return variable
                bRetval = true;
            }
            catch
            {
                // Could not get stream so close connections
                this.CloseConnection();
                // Return
                return bRetval;
            }
            // Raise event
            this.RaiseConnectionEstablishedEvent();
            //this.ConnectionEstablished();
            // Return 
            return bRetval;
        }
        public void Disconnect()
        {
            // Close connection
            this.CloseConnection();
            // Raise connectionterminated event
            this.RaiseConnectionTerminatedEvent();
            //this.ConnectionTerminated();
        }
        public bool Logon(TCPServerMemberCredentials MemberCredential)
        {
            // Define binary formatter for serialization
            BinaryFormatter bnfAuth = new BinaryFormatter();
            // Define memory stream
            MemoryStream mmsAuth = new MemoryStream();
            // Serialize credential object
            bnfAuth.Serialize(mmsAuth, MemberCredential);
            byte[] bytData = mmsAuth.GetBuffer();
            // Send credential data
            try
            {
                // Check if network stream is null
                if (nstClient != null)
                {
                    // Check if network stream can write
                    if (nstClient.CanWrite)
                    {
                        nstClient.Write(bytData, 0, bytData.Length);
                        // Return
                        return true;
                    }
                }
            }
            catch
            {
            }
            // Return
            return false;
        }
        public bool SendMessage(string Message)
        {
            // Check if connected
            if (!this.Connected)
            {
                // Return
                return false;
            }
            // Try to send message
            try
            {
                // Check if network stream is null
                if (nstClient != null)
                {
                    // Check if network stream can write
                    if (nstClient.CanWrite)
                    {
                        // Convert message to byte array
                        //byte[] bytData = Encoding.ASCII.GetBytes(Message);
                        byte[] bytData = this.TextEncoding.GetBytes(Message);
                        // Send message asyncronously
                        nstClient.BeginWrite(bytData, 0, bytData.Length, new AsyncCallback(SendingMessage), nstClient);
                        // Return
                        return true;
                    }
                }
            }
            catch
            {
            }
            // Return
            return false;
        }
        #endregion

        #region IDisposable Members
        public void Dispose()
        {
            // Close open connections
            this.CloseConnection();
            // Dispose objects
            this.nstClient.Dispose();            
        }
        #endregion
    }
}
